import time
import threading
from PySide6.QtCore import QObject, Signal
import snap7

class PLCClient(QObject):
    """
    西门子PLC通信客户端类
    使用snap7模块连接到S7-1200 PLC并读取I/O状态
    """
    # 定义信号 - 当I0.0状态改变时触发
    io_state_changed = Signal(bool)
    
    def __init__(self, ip_address='192.168.0.20', rack=0, slot=1, db_number=None):
        """
        初始化PLC客户端
        
        参数:
            ip_address: PLC的IP地址
            rack: 机架号，默认为0
            slot: 插槽号，默认为1
            db_number: 数据块编号，如果需要读取DB块则提供
        """
        super().__init__()
        self.ip_address = ip_address
        self.rack = rack
        self.slot = slot
        self.db_number = db_number
        self.client = None
        self.connected = False
        self.running = False
        self.io_thread = None
        self.last_io_state = None
        
    def connect(self):
        """连接到PLC"""
        try:
            import snap7
            self.client = snap7.client.Client()
            self.client.connect(self.ip_address, self.rack, self.slot)
            self.connected = True
            print(f"已成功连接到PLC: {self.ip_address}")
            return True
        except Exception as e:
            print(f"连接PLC失败: {str(e)}")
            self.connected = False
            return False
            
    def disconnect(self):
        """断开PLC连接"""
        if self.connected and self.client:
            try:
                self.stop_monitoring()
                self.client.disconnect()
                self.connected = False
                print("已断开PLC连接")
            except Exception as e:
                print(f"断开连接时发生错误: {str(e)}")
    
    def read_io(self, byte_index=0, bit_index=0):
        """
        读取指定的输入位状态
        
        参数:
            byte_index: 字节索引，默认为0 (I0.x)
            bit_index: 位索引，默认为0 (I0.0)
            
        返回:
            布尔值，表示输入位的状态
        """
        if not self.connected or not self.client:
            print("未连接到PLC")
            return None
            
        try:
            # 读取输入字节
            result = self.client.read_area(
                snap7.type.Area.PE,  # 输入区域
                0,                     # CPU编号 (通常为0)
                byte_index,            # 字节偏移
                1                      # 读取1个字节
            )
            
            # 提取指定位
            bit_value = bool((result[0] >> bit_index) & 1)
            return bit_value
            
        except Exception as e:
            print(f"读取输入位时发生错误: {str(e)}")
            return None
    
    def start_monitoring(self, interval=0.5):
        """
        开始监控I0.0状态，当状态变化时发出信号
        
        参数:
            interval: 轮询间隔，单位秒
        """
        if self.running:
            return
            
        self.running = True
        self.io_thread = threading.Thread(
            target=self._monitor_io_loop, 
            args=(interval,),
            daemon=True
        )
        self.io_thread.start()
        print(f"开始监控PLC输入I0.0，轮询间隔: {interval}秒")
    
    def stop_monitoring(self):
        """停止监控I0.0状态"""
        self.running = False
        if self.io_thread:
            # 等待线程终止，但最多等待1秒
            self.io_thread.join(1.0)
            print("已停止监控PLC输入")
    
    def _monitor_io_loop(self, interval):
        """
        监控I0.0状态的后台循环
        
        参数:
            interval: 轮询间隔，单位秒
        """
        try:
            import snap7
            while self.running:
                if not self.connected:
                    time.sleep(interval)
                    continue
                    
                try:
                    # 读取I0.0状态
                    io_state = self.read_io(0, 0)
                    
                    # 如果状态发生改变，发出信号
                    if io_state is not None and io_state != self.last_io_state:
                        self.last_io_state = io_state
                        self.io_state_changed.emit(io_state)
                        print(f"I0.0状态改变为: {'1' if io_state else '0'}")
                        
                except Exception as e:
                    print(f"监控循环中发生错误: {str(e)}")
                    
                # 等待指定的间隔时间
                time.sleep(interval)
                
        except Exception as e:
            print(f"监控线程遇到异常: {str(e)}")
            self.running = False
    
    def __del__(self):
        """析构函数，确保断开连接"""
        self.disconnect()
